package com.ladlee.oauth2;


import com.ladlee.oauth2.entrypoint.AuthExceptionEntryPoint;
import com.ladlee.properties.OAuth2ClientProperties;
import com.ladlee.properties.OAuth2Properties;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.sql.DataSource;
import java.util.concurrent.TimeUnit;

/**
 * @Auther: dingchang
 * @Date: 2018/12/27 15:16
 * @Description: 认证服务实现逻辑
 */
@Configuration
@EnableAuthorizationServer //开启认证服务器
public class LadleeAuthorizationConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    OAuth2Properties oAuth2Properties;

    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    UserDetailsService userDetailsService;

    @Autowired
    DataSource dataSource;

    @Autowired
    PasswordEncoder bCryptPasswordEncoder;

    /**
     * AuthorizationServerSecurityConfigurer：用来配置令牌端点(Token Endpoint)的安全约束.
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        /**
         * "AuthorizationServerSecurityConfigurer 配置了获取 Token 的策略，在本案例中对获取 Token
         * 请求不进行拦截，只需要验证获取 Token 的验证信息，这些信息准确无误，就返回 Token 。另
         * 外配置了检查 Token 的策略
         */
        /* 配置token获取合验证时的策略 */
        //enable client to get the authenticated when using the /oauth/token to get a access token
        //there is a 401 authentication is required if it doesn't allow form authentication for clients when access /oauth/token
        security
                .allowFormAuthenticationForClients() //主要是让/oauth/token支持client_id以及client_secret作登录认证
                .tokenKeyAccess("permitAll()") // 开启/oauth/token_key验证端口无权限访问
                .checkTokenAccess("isAuthenticated()") //开启/oauth/check_token验证端口认证权限访问
                .passwordEncoder(new BCryptPasswordEncoder())
                .authenticationEntryPoint(new AuthExceptionEntryPoint());
    }

    /**
     * 用来配置客户端详情服务（ClientDetailsService），客户端详情信息在这里进行初始化，你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //clients.jdbc(dataSource).passwordEncoder(passwordEncoder());//这个地方指的是从jdbc查出数据来存储
        //clients.withClientDetails(clientDetails());//这个地方指的是从jdbc查出数据来存储
        //将配置的客户端信息储存在内存中
        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
        if(ArrayUtils.isNotEmpty(oAuth2Properties.getClients())){
            for (OAuth2ClientProperties config : oAuth2Properties.getClients()){
                builder.withClient(config.getClientId())
                        .secret(bCryptPasswordEncoder.encode(config.getClientSecret()))
                        .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds()) // 令牌有效时间
                        .refreshTokenValiditySeconds(60 * 60 * 24 * 15) //刷新令牌有效时间
                        .authorizedGrantTypes("refresh_token", "password", "authorization_code") //OAuth2支持的验证模式
                        .scopes("all");
            }
        }

    }

    /**
     * AuthorizationServerEndpointsConfigurer：用来配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)。
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())//token 存储方式()
                .authenticationManager(authenticationManager) //需要配置 AuthenticationManager 这个Bean 这个 Bean 来源于 WebSecurityConfigurerAdapter 中的配 ，只有配置了这个 Bean 才会开启密码类型的验证
                .userDetailsService(userDetailsService); //用来读取验证用户的信息。

        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }

    @Bean
    @ConditionalOnMissingBean(TokenStore.class)
    public TokenStore tokenStore(){
        JdbcTokenStore jdbcTokenStore = new JdbcTokenStore(dataSource);
        return jdbcTokenStore;
    }

    @Bean
    public ClientDetailsService clientDetails() {
        JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
        return jdbcClientDetailsService;
    }
}
